USB Communication

The Human Interface Device (HID) class is used to communicate with the device. The main benefit is that there are no drivers needed.
The drawback is low communication speed, but this is OK for this use case as there is not much data transmitted.
Note that the HID protocol differentiates between input reports, output reports and feature reports.
Input reports (e.g. endpoint 1) are usually periodic, i.e. the operating system of the master (e.g. Windows) will poll the input report at the interval defined in the device's configuration descriptor.
It is also possible to read the input report via a control request on demand (e.g. via endpoint 0), but due to the periodic nature of the input report, this can lead to bandwidth and buffering issues.
Indeed, while reading input reports via control requests worked in a C# prototype, it didn't work via the Java HID API.
As a workaround, I decided to (ab-)use the feature report to communicate with the device instead. The input report is just used for a small period message which contains the current state and engine speed.
So the size of the (periodic) input report is limited to 3 bytes (8bit state followed by 16bit engine speed), while the size of the feature report is set to 32 bytes.

Protocol

The communication is always initiated by the master (PC) and starts with an 8bit command. The following 31 bytes can be used for data belonging to the command.
The device always answers with ACK|command (ACK = 0x80 is ORed with command), followed by a status (0: OK else specific fault status code). The following 30 bytes are command specific (i.e. containing requested data).
Transferring data from/to the four channels requires channel specific commands. The 2bit channel information is then coded into the two lowermost bits of the command.
E.g. INIT_READ_CH0 is 8 (8|0) and INIT_READ_CH3 is 11 (8|3).
Note that 16bit and 32bit values are transferred little endian (lowest byte first, highest byte last).

Fault status codes

FAULT_OK 0
FAULT_CTR_MISMATCH 1
FAULT_NVRAM_SIZE 2
FAULT_NVRAM_INVALID 3
FAULT_NVRAM_BUSY 4

Commands

Write channel data to device

INIT_WRITE_CH0 0
INIT_WRITE_CH1 1
INIT_WRITE_CH2 2
INIT_WRITE_CH3 3
WRITE_CH0 4
WRITE_CH1 5
WRITE_CH2 6
WRITE_CH3 7


Read channel data from device

INIT_READ_CH0 8
INIT_READ_CH1 9
INIT_READ_CH2 10
INIT_READ_CH3 11
READ_CH0 12
READ_CH1 13
READ_CH2 14
READ_CH3 15


Write gradient commands

INIT_WRITE_GRAD 0x10
WRITE_GRAD 0x11
START_GRAD 0x12
STOP_GRAD 0x13
Read gradient commands
INIT_READ_GRAD 0x18
READ_GRAD 0x19


NVRAM commands

GET_NVRAM_STATE 0x20
WRITE_NVRAM 0x21
READ_NVRAM 0x22
CLEAR_NVRAM 0x23


Engine speed commands

START 0x40
STOP 0x41
GET_N 0x42
SET_N 0x43
SET_BIDIR_SETUP 0x44
GET_BIDIR_SETUP 0x45
UPDATE_MODES 0x48
SET_GLITCHES 0x49


Set PWM commands

SET_PWM_CH0 0x50
SET_PWM_CH1 0x51
SET_PWM_CH2 0x52
SET_PWM_CH3 0x53


Get PWM commands

GET_PWM_CH0 0x54
GET_PWM_CH1 0x55
GET_PWM_CH2 0x56
GET_PWM_CH3 0x57


Get Revision command

GET_REVISION 0x7f

 

Command details

Get Revision

Reads back the revision from the device.

PC sends

    GET_REVISION (U8)

Device answers
    GET_REVISION|ACK (U8), FAULT_OK (U8), REVISION (U32)

In the replied revision each byte is a decimal place. E.g. 0x01020310 -> 1.2.3.16

Write data to channel X

Writing channel data has to be initialized with an INIT_WRITE command, where the device's answer will contain the number N of transfers needed.
The actual data transfer is then done via N READ commands.

PC sends
    INIT_WRITE_CHx (U8), NUM_OF_DATA (U16), OFFSET (U32), 1st_EDGE (U8), MODE (U8), NAME (16xU8)

    NUM_OF_DATA is the number of U32 values (periods in internal tick resolution)
    OFFSET is the offset of the 1st edge based on the absolute 0 position (in internal tick resolution)
    1st_EDGE is the polarity of this 1st edge (0: falling, 1: rising)
    MODE can be angular (0), time (1) or PWM (2)
    NAME is a string of up to 16 ASCII characters, If less characters are used, the string must be zero terminated.

Device answers
    INIT_WRITE_CHx|ACK (U8), FAULT_OK (U8), NUMBER_OF_PACKETS_NEEDED (U16)

PC sends (NUMBER_OF_PACKETS_NEEDED times)
    WRITE_CHx (U8), PACKET_COUNTER (U16), DATA (N*U32)

    PACKET_COUNTER is a counter starting with 0 used to validate the order of packets.
    DATA contains the 1 to 7 U32 values (periods in internal tick resolution)

Device answers (each time)
    WRITE_CHx|ACK (U8), STATUS/FAULT (U8), RECEIVED_CTR, EXPECTED_CTR

    In case of an error, the status will be FAULT_CTR_MISMATCH and the both counters (received/expected) won't match.
    Note that an init write command stop the output signal creation. It has to be started again with the start command.

Read data from channel X

Reading has to be initialized with an INIT_READ command, where the device's answer will contain the number N of transfers needed.
The actual data transfer is then done via N READ commands.

PC sends
    INIT_READ_CHx (U8)

Device answers
    INIT_READ_CHx|ACK (U8), FAULT_OK (U8), NUMBER_OF_PACKETS_NEEDED (U16), NUMBER_OF_DATA (U16), OFFSET (U32), 1st_EDGE (U8), MODE (U8), NAME (16xU8)

PC sends (NUMBER_OF_PACKETS_NEEDED times)
    READ_CHx (U8), PACKET_COUNTER (U16)

Device answers (each time)
    READ_CHx|ACK (U8), STATUS/FAULT (U8), RECEIVED_CTR, EXPECTED_CTR, DATA

Start output signal creation

Start the output after it was stopped e.g. due to writing channel data.

PC sends
    START (U8)

Device answers
    START|ACK(U8), FAULT_OK (U8)

Set engine speed

Set a new value for engine speed.

PC sends
    SET_N (U8), ENGINE_SPEED (S16)

Device answers
    SET_N|ACK(U8), FAULT_OK (U8)

Get engine speed

Read back current value for engine speed.

PC sends
    GET_N (U8)

Device answers
    GET_N|ACK(U8), FAULT_OK (U8), ENGINE_SPEED (S16)

This command is not really needed, as the input report transfers the current engine speed permanently.

Write Gradient

Send gradient data to the device. Note: when sending gradient data, the output is not stopped.

PC sends
    INIT_WRITE_GRAD (U8), NUM_OF_DATA (U16)

Device answers
    INIT_WRITE_GRAD|ACK (U8), FAULT_OK (U8), NUMBER_OF_PACKETS_NEEDED (U16)

PC sends (NUMBER_OF_PACKETS_NEEDED times)
    WRITE_GRAD (U8), PACKET_COUNTER (U16), DATA (N*(U32+S16))

DATA: pairs of U32 period in milliseconds, S16 engine speed

Device answers (each time)
    WRITE_GRAD|ACK (U8), STATUS/FAULT (U8), RECEIVED_CTR, EXPECTED_CTR

Read Gradient

Read back the current gradient from the device.

PC sends
    INIT_READ_GRAD (U8)

Device answers
    INIT_READ_GRAD|ACK (U8), FAULT_OK (U8), NUMBER_OF_PACKETS_NEEDED (U16), NUMBER_OF_DATA (u16)

PC sends (NUMBER_OF_PACKETS_NEEDED times)
    READ_GRAD (U8), PACKET_COUNTER (U16)

Device answers (each time)
    READ_GRAD|ACK (U8), STATUS/FAULT (U8), RECEIVED_CTR, EXPECTED_CTR, DATA
DATA: pairs of U32 period in milliseconds, S16 engine speed

Start Gradient

Start an engine speed gradient.

PC sends
    START_GRAD (U8)

Device answers     START_GRAD|HID_CMD_ACK (U8), HID_FAULT_OK (U8)

Stop Gradient

Stops a running engine speed gradient.

PC sends
    STOP_GRAD (U8)

Device answers     STOP_GRAD|HID_CMD_ACK (U8), HID_FAULT_OK (U8)

Write NVRAM

Write the current channel data etc. from device RAM to NVRAM.

PC sends

    WRITE_NVRAM (U8)
   
Device answers
    WRITE_NVRAM|ACK (U8), STATUS/FAULT (U8), NUMBER_OF_DATA (u16)

The device calculates the number of needed bytes and reports OK if writing to NVRAM is possible.
The number of bytes are just reported for a progress display.
Beware: writing is not immediately finished and has to be polled via command GET_NVRAM_STATE.

STATUS:     FAULT_OK                  writing is possible
                    FAULT_NVM_SIZE    not enough space in NVRAM
                    FAULT_NVM_BUSY device is busy reading or writing to NVRAM

Read NVRAM

Read channel data etc. from NVRAM to device RAM.

PC sends
    READ_NVRAM (U8)
   
Device answers
    READ_NVRAM|ACK (U8), STATUS/FAULT (U8), NUMBER_OF_DATA (u16)

The device calculates the number of bytes used in NVRAM and reports OK if a valid header was found in NVRAM.
Beware: writing is not immediately finished and has to be polled via command GET_NVRAM_STATE.

STATUS:    FAULT_OK                          reading is possible
                    FAULT_NVM_INVALID    no valid header found in NVRAM (empty or corrupt) or CRC error in data section
                    FAULT_NVM_BUSY         device is busy reading or writing to NVRAM

Clear NVRAM

Clear the NVRAM by overwriting the header with zeros.

PC sends
    CLEAR_NVRAM (U8)
   
Device answers
    CLEAR_NVRAM|ACK (U8), STATUS/FAULT (U8), NUMBER_OF_DATA (u16)

The number of bytes are just reported for a progress display. Status will always be OK
Beware: clearing is not immediately finished and has to be polled via command GET_NVRAM_STATE.

Get NVRAM state

Read status of NVRAM - also needed during pending read/write/clear operation.

PC sends
    GET_NVRAM_STATE (U8)
   
Device answers
    GET_NVRAM_STATE|ACK (U8), NVRAM_STATE (U8), NUMBER_OF_DATA (u16)

NVRAM_STATE:       
    NVRAM_INVALID_ST    0
    NVRAM_READY_ST      1
    NVRAM_READ_ST         2
    NVRAM_WRITE_ST       3
    NVRAM_CLEAR_ST       4

During READ/WRITE/CLEAR operation, NUMBER_OF_DATA contains the number of bytes to be processed, else 0.

Set PWM channel

Sets duty cycle, period  and polarity for a PWM channel.

PC sends
    SET_PWM_CHx (U8), POLARITY (U8), PERIOD (U32), DUTYPERIOD (U32)

Device answers
    SET_PWM_CHx|ACK (U8), FAULT_OK (U8)

PERIOD and DUTYPERIOD are in timer tick resolution. POLARITY is 0 for low active and 1 for high active.

Get PWM channel

Reads back duty cycle, period  and polarity of a PWM channel.

PC sends
    GET_PWM_CHx (U8)

Device answers
    GET_PWM_CHx|ACK (U8), FAULT_OK (U8), POLARITY (U8), PERIOD (U32), DUTYPERIOD (U32)

PERIOD and DUTYPERIOD are in timer tick resolution. POLARITY is 0 for low active and 1 for high active.

Update Modes

Sets the modes for all four channels at once.

PC sends
    UPDATE_MODES (U8), MODE CH0 (U8), MODE CH1 (U8), MODE CH2 (U8), MODE CH3 (U8)

Device answers
    UPDATE_MODES|ACK (U8), FAULT_OK (U8)

Supported modes are:
ANGULAR    0
TIME              1
PWM              2

Set BiDir Setup

Sets new device configuration for bidirectional crank simulation.

PC sends
    SET_BIDIR_SETUP (U8), REV_ENABLE (U8), BIDIR_ENABLE (U8), ACTIVE_EDGE (U8), FWD_PERIOD (U32), REV_PERIOD (U32)

Device answers
    SET_BIDIR_SETUP|ACK (U8), FAULT_OK (U8)

REV_ENABLE enabled (1) or disables the possibility of negative engine speeds (inverted rotation direction)
BIDIR_ENABLE enables (1) or disables (0) the bidirectional simulation for channel 0.
ACTIVE_EDGE defines falling edge (0) or rising edge (1) as active edge (i.e. falling edge means the active period will be low)
FWD_PERIOD defines the active period for forward rotation (in timer ticks)
REV_PERIOD defines the active period for reverse rotation (in timer ticks)

Get BiDir Setup

Reads back device configuration for bidirectional crank simulation.

PC sends
    GET_BIDIR_SETUP (U8)

Device answers
    GET_BIDIR_SETUP|ACK (U8), FAULT_OK (U8), REV_ENABLE (U8), BIDIR_ENABLE (U8), ACTIVE_EDGE (U8), FWD_PERIOD (U32), REV_PERIOD (U32)

BIDIR_ENABLE enables (1) or disables (0) the bidirectional simulation for channel 0.
ACTIVE_EDGE defines falling edge (0) or rising edge (1) as active edge (i.e. falling edge means the active period will be low)
FWD_PERIOD defines the active period for forward rotation (in timer ticks)
REV_PERIOD defines the active period for reverse rotation (in timer ticks)

Create glitches

Create glitches by pulling the masked channel to the given direction for a certain time (repeat with period)

PC sends
    SET_GLITCHES (U8), CH_ENABLE_MASK (U8), CH_POLARITY_MASK (U8), COUNT (U8), DURATION (U32), PERIOD (U32)

Device answers
    SET_GLITCHES|ACK (U8), FAULT_OK (U8)

CH_ENABLE_MASK: set channel bit (1<<ch) to 1 to create a glitch on the channel
CH_POLARITY_MASK: pull enabled channel low (0) or high (1)
COUNT: repeat the glitch creation count times.
DURATION: duration of one glitch in timer ticks
PERIOD: interval between two glitches (from start to start) in timer ticks - must be larger than DURATION


Back to main page